// TODO: CODE REVIEW & CLEANUP!
//
// SimulationOnClientSupport.cs
//
// Implements types used to support implementation of assemblies that simulate a HomeUX server
// within a Silverlight client application.
//

using System;
using System.IO;
using System.Windows.Threading;
using HomeUX.ClientControlSupport;
using HomeUX.DriverModel;
using HomeUX.Networking;
using HomeUX.Server;
using HomeUX.Synchronization;
using HomeUX.Utilities;

namespace HomeUX.SimulationOnClientSupport
{

/// <summary>
/// TODO
/// </summary>
///
public abstract class ServerSimulatorBase
{
    public abstract Stream GetConfigStream();
}

// this is a simulated client-side communication endpoint; when you call
// IMessageEndpoint.SendMessage, instead of transmitting the message over the network to the
// server, the message goes to the built-in simulated server which is implemented by this
// class
class SimulationEndpoint : IMessageEndpoint, IDisposable
{
    Dispatcher _dispatcher;

    SyncServer _simulatedServer =
        new SyncServer()
        {
            WorkQueue = new WorkQueue("ServerSimulator"),
            LogDestination = new SimulationLogDestination()
        };

    class SimulationLogDestination : ILogDestination
    {
        string ILogDestination.LogInfo(string format, params object[] args)
        {
            string message = Util.Format(format, args);
            Util.Trace(message);
            return message;
        }

        string ILogDestination.LogWarning(string format, params object[] args)
        {
            string message = Util.Format(format, args);
            Util.Trace("WARNING: {0}", message);
            return message;
        }

        string ILogDestination.LogError(string format, params object[] args)
        {
            string message = Util.Format(format, args);
            Util.Trace("ERROR: {0}", message);
            return message;
        }
    }

    ServerSimulatorBase _serverSimulator;

    ServerEndpoint _simulatedEndpoint;

    SyncClientAgent _simulatedAgent;

    public SimulationEndpoint(Dispatcher dispatcher, ServerSimulatorBase serverSimulator)
    {
        _serverSimulator = serverSimulator;
        _dispatcher = dispatcher;
        _simulatedEndpoint = new ServerEndpoint(ShortSessionIdManager.PredefinedSessionIds[1],
            SendMessageToClient);
    }

    //Action _simulationStarted;

    public event EventHandler SimulationStarted;

    public void StartSimulation()
    {
        // start drivers etc. on the server simulation thread
        WorkItem item = _simulatedServer.WorkQueue.Add(StartSimulationOnWorkQueueThread);
    }

    public void Dispose()
    {
        // stop drivers etc. on the server simulation thread
        WorkItem item = _simulatedServer.WorkQueue.Add(StopSimulationOnWorkQueueThread);
        _simulationStoppedEvent.WaitOne();
    }

    //System.Threading.ManualResetEvent _simulationStartedEvent = new System.Threading.ManualResetEvent(false);
    System.Threading.ManualResetEvent _simulationStoppedEvent = new System.Threading.ManualResetEvent(false);

    void StartSimulationOnWorkQueueThread()
    {
        // NOTE: this method runs on a WorkQueue thread, not the UI thread
        try
        {
            //_simulatedEndpoint.SendMessage = SendMessageToClient;
            _simulatedAgent = _simulatedServer.InitializeSimulation(_simulatedEndpoint);

            _simulatedServer.WorkQueue.ExceptionCaught += WorkQueue_ExceptionCaught;
            _simulatedServer.WorkQueue.CatchExceptionTypes(typeof(SyncException));

            // read App.config file into <_simulatedServer.Configuration>
            using (Stream configFileStream = _serverSimulator.GetConfigStream())
            //using (Stream configFileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(
            //    "HomeUX.ServerSimulator.App.config"))
            {
                using (StreamReader streamReader = new StreamReader(configFileStream))
                    _simulatedServer.ReadConfigurationFile(streamReader, "<SimulationConfiguration>");
            }

            // the following is the simulation equivalent of SyncServer.Start
            _simulatedServer.ReadGeneralSettings();
            _simulatedServer.Start();
            foreach (ConfigurationElement element in _simulatedServer.Configuration.Children)
            {
#if false
                if (element.Name == "AggregatorFunction")
                {
                    string functionName = element.GetRequiredAttribute("Name", null);
                    AggregatorFunctionFactory factory;
                    try
                    {
                        switch (functionName)
                        {

                        case "ChannelMixer":

                            factory = new ChannelMixerFactory();
                            break;

                        default:

                            factory = null; // ignore this aggregator function -- it's not simulated 
                            break;

                        }

                        if (factory != null)
                        {
                            factory.SetRootConfigurationElement(element);

                            try
                            {
                                factory.OnInitialize(element);
                            }
                            catch (Exception ex)
                            {
                                if (ex is FatalException)
                                {
                                    throw new ConfigurationException(ex, element,
                                        "Error initializing factory for aggregator function \"{0}\"",
                                        functionName);
                                }
                                else
                                    throw;
                            }

                            factory.CheckForExtraAttributes();

                            _simulatedServer.AddAggregatorFunctionFactory(functionName, factory);
                        }
                    }
                    catch (Exception ex)
                    {
                        ex = ConfigurationException.ToFatalException(ex, "App.config");
                        if (ex is FatalException)
                        {
                            throw new FatalException(ex,
                                "Error initializing factory for aggregator function \"{0}\"",
                                functionName);
                        }
                        else
                            throw;
                    }
                }
                else
#endif
                if (element.Name == "Device")
                {
                    string deviceName = element.GetRequiredAttribute("Name");
                    string driverSpec = element.GetRequiredAttribute("Driver");
                    Driver device;
                    try
                    {
                        try
                        {
                            device = Util.CreateInstance<Driver>(element.GetRequiredAttribute("Driver"));
                        }
                        catch (CreateInstanceException)
                        {
                            device = null;
                        }
                        if (device != null)
                        {
                            device.Initialize(CreateSimulatedDeviceParameters(deviceName, element));
                            _simulatedServer.AddDevice(device);
                        }
                    }
#if !NOCATCH
                    catch (Exception ex)
                    {
                        ex = ConfigurationException.ToFatalException(ex, "App.config");
                        if (ex is FatalException)
                        {
                            throw new FatalException(ex, "Error initializing device \"{0}\"",
                                deviceName);
                        }
                        else
                            throw;
                    } // exception here? try defining NOCATCH
#else
                    finally
                    {
                    }
#endif
                }
#if false
				else
                if (element.Name == "Aggregator")
                {
                    string deviceName = element.GetRequiredAttribute("Name", null);
                    try
                    {
                        var device = new AggregatorDriver();
                        device.Initialize(CreateSimulatedDeviceParameters(deviceName, element));
                        _simulatedServer.AddDevice(device);
                    }
                    catch (Exception ex)
                    {
                        ex = ConfigurationException.ToFatalException(ex, "App.config");
                        if (ex is FatalException)
                        {
                            throw new FatalException(ex, "Error initializing aggregator device \"{0}\"",
                                deviceName);
                        }
                        else
                            throw;
                    }
                }
#endif
            }
        }
#if !NOCATCH
        catch (Exception ex)
        {
            ex = ConfigurationException.ToFatalException(ex, "App.config");
            if (ex is FatalException)
                DisplayExceptionAlert(ex);
            else
                throw;
        } // exception here? try defining NOCATCH
#endif
        finally
        {
            //_simulationStartedEvent.Set();
            _dispatcher.BeginInvoke(delegate
            {
                if (SimulationStarted != null)
                    SimulationStarted(this, EventArgs.Empty);
            });
        }
    }

    void StopSimulationOnWorkQueueThread()
    {
        // NOTE: this method runs on a WorkQueue thread, not the UI thread
        try
        {
            _simulatedServer.Stop();

            // the WorkQueue is owned by the application, not by SyncServer, so the application must
            // be responsible for cleanup
            WorkQueue workQueue = _simulatedServer.WorkQueue;
            if (workQueue != null)
            {
                _simulatedServer.WorkQueue = null;
                workQueue.Dispose();
            }
        }
        finally
        {
            _simulationStoppedEvent.Set();
        }
    }

    void WorkQueue_ExceptionCaught(object sender, ExceptionCaughtEventArgs e)
    {
        DisplayExceptionAlert(e.Exception);
    }

    void DisplayExceptionAlert(Exception ex)
    {
        _dispatcher.BeginInvoke(delegate
        {
            ClientGlobals.Alert(AlertType.Warning,
                "Simulated server error: {0}", Util.FormatExceptionMessage(ex));
        });
    }

    DeviceParameters CreateSimulatedDeviceParameters(string deviceName, ConfigurationElement configurationElement)
    {
        DeviceParameters param = _simulatedServer.CreateDeviceParameters(deviceName);
        param.Configuration = configurationElement;
        return param;
    }

    bool IMessageEndpoint.IsConnected
    {
        get
        {
            return true;
        }
    }

    event EventHandler _isConnectedChanged;

    event EventHandler IMessageEndpoint.IsConnectedChanged
    {
        add
        {
            _isConnectedChanged += value;
        }
        remove
        {
            _isConnectedChanged -= value;
        }
    }

    public void FireIsConnectedChanged()
    {
        if (_isConnectedChanged != null)
            _isConnectedChanged(this, EventArgs.Empty);

        SendMessageToClient(_simulatedServer.CreateClientFullUpdateMessage(_simulatedEndpoint.SessionId));
    }

    // sends a message from the simulated server back to the client
    void SendMessageToClient(Message message)
    {
        _dispatcher.BeginInvoke(delegate
        {
            if (_messageReceived != null)
            {
                _messageReceived(this, new MessageReceivedEventArgs()
                {
                    Message = message
                });
            }
        });
    }

    // sends a message to the simulated server
    bool IMessageEndpoint.SendMessage(Message message)
    {
        _simulatedServer.WorkQueue.Add(delegate
        {
            _simulatedServer.OnMessageReceived(_simulatedAgent, message);
        });
        return true;
    }

    event EventHandler<MessageReceivedEventArgs> _messageReceived;

    event EventHandler<MessageReceivedEventArgs> IMessageEndpoint.MessageReceived
    {
        add
        {
            _messageReceived += value;
        }
        remove
        {
            _messageReceived -= value;
        }
    }
}

}

